package utilities {
	
	import flash.geom.Point;
	
	/**
	 * uMath is an utility class for extended Math functions.
	 * 
	 * @package		utilities
	 * @author		Nathan Lucas <monk4688@gmail.com>
	 * @version		1.1.0
	 */	
	public class uMath {
		
		public function uMath() {
			throw new Error("uMath cannot be instantiated. Use uMath.method()");
		}
		
		/**
		 * Round Number x to d decimal places.
		 * 
		 * @access	public
		 * @type	static
		 * @param	x		Number to round.
		 * @param	d		Decimal places.
		 * @return	Number
		 */
		public static function round(x:Number, d:Number = 0):Number {
			var md:Number = Math.pow(10, d);
			return (Math.round(x * md) / md);
		}
		
		/**
		 * Floor Number x to d decimal places.
		 * 
		 * @access	public
		 * @type	static
		 * @param	x		Number to floor.
		 * @param	d		Decimal places.
		 * @return	Number
		 */
		public static function floor(x:Number, d:Number = 0):Number {
			var md:Number = Math.pow(10, d);
			return (Math.floor(x * md) / md);
		}
		
		/**
		 * Ceil Number x to d decimal places.
		 * 
		 * @access	public
		 * @type	static
		 * @param	x		Number to ceil.
		 * @param	d		Decimal places.
		 * @return	Number
		 */
		public static function ceil(x:Number, d:Number = 0):Number {
			var md:Number = Math.pow(10, d);
			return (Math.ceil(x * md) / md);
		}
		
		/**
		 * Get random number between x..y
		 * 
		 * @access	public
		 * @type	static
		 * @param	x		Number min.
		 * @param	y		Number max.
		 * @return	Number
		 */
		public static function random(x:Number, y:Number, d:Number = 0):Number {
			if (d == 0) {
				return (uMath.floor(Math.random() * (y - x + 1), d) + x);
			}
			return uMath.floor((Math.random() * (y - x) + x), d);
		}
		
		/**
		 * Binomial coefficient. n choose k
		 * 
		 * @access	public
		 * @type	static
		 * @param	n		Number
		 * @param	k		Number
		 * @return	Number
		 */
		public static function choose(n:Number, k:Number):Number {
			if (k > n) {
				return 0;
			}
			if ((n - k) < k) {
				return uMath.choose(n, (n - k));
			}
			return (uMath.factorial(n) / (uMath.factorial(k) * uMath.factorial(n - k)));
		}
		
		/**
		 * Factorial of x.
		 * 
		 * @access	public
		 * @type	static
		 * @param	n		Number
		 * @return	Number
		 */
		public static function factorial(n:Number):Number {
			if (n == 0) {
				return 1;
			}
			return uMath.product(1, n, function(i:Number):Number {
				return i;
			});
		}
		
		/**
		 * Bernstein polynomial.
		 * 
		 * @access	public
		 * @type	static
		 * @param	n		Number
		 * @param	v		Number
		 * @param	x		Number
		 */
		public static function bernstein(n:Number, v:Number, x:Number):Number {
			return (uMath.choose(n, v) * Math.pow(x, v) * Math.pow((1 - x), (n - v)));
		}
		
		/**
		 * Run summation.
		 * 
		 * @usage	var n:Number = uMath.sum(2, 6, function(i:Number):Number {
		 * 				return Math.pow(i, 2);
		 * 			});
		 * 			//n = 90
		 * 
		 * @usage	var n:Array = uMath.sum(2, 6, function(i:Number):Number {
		 * 				return [Math.pow(i, 2), i];
		 * 			});
		 * 			//n = Array[90, 6];
		 * 
		 * 
		 * @access	public
		 * @type	static
		 * @param	m		Lower bounds.
		 * @param	n		Upper bounds.
		 * @param	f		Function that preforms calculations.
		 * @return	*		(Number or Array)
		 */
		public static function sum(m:Number, n:Number, f:Function):* {
			var s:Array;
			var _t:* = f(0);
			s = (typeof _t == "number") ? uMath.array(1, 0) : uMath.array(_t.length, 0);
			var _s:*;
			for (var i:Number = m; i <= n; i++) {
				_s = f(i);
				for (var j:int = 0; j < s.length; j++) {
					s[j] += (typeof _s == "number") ? _s : _s[j];
				}
			}
			return (typeof _t == "number") ? s[0] : s;
		}
		
		/**
		 * Run product.
		 * 
		 * @usage	var n:Number = uMath.product(2, 6, function(i:Number):Number {
		 * 				return Math.pow(i, 2);
		 * 			});
		 * 			//n = 518400
		 * 
		 * @usage	var n:Array = uMath.product(2, 6, function(i:Number):Number {
		 * 				return [Math.pow(i, 2), i];
		 * 			});
		 * 			//n = Array[518400, 6];
		 * 
		 * 
		 * @access	public
		 * @type	static
		 * @param	m		Lower bounds.
		 * @param	n		Upper bounds.
		 * @param	f		Function that preforms calculations.
		 * @return	*		(Number or Array)
		 */
		public static function product(m:Number, n:Number, f:Function):* {
			var p:Array;
			var _t:* = f(0);
			p = (typeof _t == "number") ? uMath.array(1, 1) : uMath.array(_t.length, 1);
			var _p:*;
			for (var i:Number = m; i <= n; i++) {
				_p = f(i);
				for (var j:int = 0; j < p.length; j++) {
					p[j] *= (typeof _p == "number") ? _p : _p[j];
				}
			}
			return (typeof _t == "number") ? p[0] : p;
		}
		
		/**
		 * Convert degrees (x) to radians.
		 * 
		 * @access	public
		 * @type	static
		 * @param	x		Degrees.
		 * @return	Number
		 */
		public static function radians(x:Number):Number {
			return (x * (Math.PI / 180));
		}
		
		/**
		 * Convert radians (x) to degrees.
		 * 
		 * @acces	public
		 * @type	static
		 * @param	x		Radians.
		 * @return	Number
		 */
		public static function degrees(x:Number):Number {
			return (x * (180 / Math.PI));
		}
		
		/**
		 * Find if Number n is between Numbers x..y
		 * 
		 * @access	public
		 * @type	static
		 * @param	n		Number
		 * @param	x		Number min.
		 * @param	y		Number max.
		 * @return	Boolean
		 */
		public static function between(n:Number, x:Number, y:Number):Boolean {
			if ((n > x) && (n < y)) {
				return true;
			}
			return false;
		}
		
		/**
		 * Creates array c long, filled with v.
		 * 
		 * @access	public
		 * @type	static
		 * @param	c		Array length.
		 * @param	v		Array value.
		 * @return 	Array
		 */
		public static function array(c:int, v:Number):Array {
			var a:Array = new Array(c);
			for (var i:int = 0; i < c; i++) {
				a[i] = v;
			}
			return a;
		}
		
		/**
		 * Scale Point p by n.
		 * 
		 * @access	public
		 * @type	static
		 * @param	p		Point
		 * @param	n		Number
		 * @return	Point
		 */
		public static function scalePoint(p:Point, n:Number):Point {
			return new Point((p.x * n), (p.y * n));
		}
		
		/**
		 * Add Point p1 and Point p2.
		 * 
		 * @access	public
		 * @type	static
		 * @param	p1		Point
		 * @param	p2		Point
		 * @return	Point
		 */
		public static function addPoints(p1:Point, p2:Point):Point {
			return new Point((p1.x + p2.x), (p1.y + p2.y));
		}
	}
}